From 96e049d5e88703cb694dfeb253ec6fa8452157a1 Mon Sep 17 00:00:00 2001
From: tsteven4 <13596209+tsteven4@users.noreply.github.com>
Date: Mon, 13 Apr 2020 08:27:57 -0600
Subject: [PATCH] Generalize xmlgeneric callbacks (#532)
generalize callbacks for xmlgeneric.
use newly available pointer to data member callbacks to restore
yahoo functionality.
add a yahoo test.
.
add xml_init method to automatically generate Functors.
---
reference/yahoo.csv | 2 +
reference/yahoo.xml | 17 +++++++
testo.d/yahoo.test | 4 ++
xmlgeneric.cc | 60 +++++++++++++++++++-----
xmlgeneric.h | 109 ++++++++++++++++++++++++++++++++++++++++++--
yahoo.cc | 42 ++---------------
yahoo.h | 62 +++++++++++++++----------
7 files changed, 219 insertions(+), 77 deletions(-)
create mode 100644 reference/yahoo.csv
create mode 100644 reference/yahoo.xml
create mode 100644 testo.d/yahoo.test
diff --git a/reference/yahoo.csv b/reference/yahoo.csv
new file mode 100644
index 000000000..a8f590e02
--- /dev/null
+++ b/reference/yahoo.csv
@@ -0,0 +1,2 @@
+No,Latitude,Longitude,Name
+1,37.416384,-122.024853,"701 FIRST AVE, SUNNYVALE, CA, 94089-1019, US"
diff --git a/reference/yahoo.xml b/reference/yahoo.xml
new file mode 100644
index 000000000..7425755a5
--- /dev/null
+++ b/reference/yahoo.xml
@@ -0,0 +1,17 @@
+
+
+
+
+37.416384
+
+-122.024853
+701 FIRST AVE
+SUNNYVALE
+CA
+
+94089-1019
+US
+
+
diff --git a/testo.d/yahoo.test b/testo.d/yahoo.test
new file mode 100644
index 000000000..a5e8bdc4d
--- /dev/null
+++ b/testo.d/yahoo.test
@@ -0,0 +1,4 @@
+
+gpsbabel -i yahoo -f ${REFERENCE}/yahoo.xml -o unicsv -F ${TMPDIR}/yahoo.csv
+compare ${REFERENCE}/yahoo.csv ${TMPDIR}/yahoo.csv
+
diff --git a/xmlgeneric.cc b/xmlgeneric.cc
index 15c301959..83bc27cdc 100644
--- a/xmlgeneric.cc
+++ b/xmlgeneric.cc
@@ -23,6 +23,7 @@
#include // for QHash
#include // for QIODevice, QIODevice::ReadOnly
#include // for QLatin1Char
+#include
#include // for QStringRef
#include // for QTextCodec
#include // for QXmlStreamAttributes
@@ -44,7 +45,8 @@ enum xg_shortcut {
xg_shortcut_ignore
};
-static xg_tag_mapping* xg_tag_tbl;
+static QList* xg_tag_tbl;
+static bool dynamic_tag_tbl;
static QHash* xg_shortcut_taglist;
static QString rd_fname;
@@ -65,25 +67,25 @@ static QTextCodec* codec = utf8_codec; // Qt has no vanilla ASCII encoding =(
* xml strains and insulates us from a lot of the grubbiness of expat.
*/
-xg_callback*
+XgCallbackBase*
xml_tbl_lookup(const QString& tag, xg_cb_type cb_type)
{
const QByteArray key = tag.toUtf8();
const char* keyptr = key.constData();
- for (xg_tag_mapping* tm = xg_tag_tbl; tm->tag_cb != nullptr; ++tm) {
- if ((cb_type == tm->cb_type) && str_match(keyptr, tm->tag_name)) {
- return tm->tag_cb;
+ for (const auto& tm : qAsConst(*xg_tag_tbl)) {
+ if ((cb_type == tm.cb_type) && str_match(keyptr, tm.tag_name)) {
+ return tm.tag_cb;
}
}
return nullptr;
}
void
-xml_init(const QString& fname, xg_tag_mapping* tbl, const char* encoding,
+xml_common_init(const QString& fname, const char* encoding,
const char** ignorelist, const char** skiplist)
{
rd_fname = fname;
- xg_tag_tbl = tbl;
+
xg_encoding = encoding;
if (encoding) {
QTextCodec* tcodec = QTextCodec::codecForName(encoding);
@@ -91,6 +93,7 @@ xml_init(const QString& fname, xg_tag_mapping* tbl, const char* encoding,
codec = tcodec;
}
}
+
xg_shortcut_taglist = new QHash;
if (ignorelist != nullptr) {
for (; ignorelist && *ignorelist; ++ignorelist) {
@@ -104,14 +107,47 @@ xml_init(const QString& fname, xg_tag_mapping* tbl, const char* encoding,
}
}
+void
+xml_init(const QString& fname, QList* tbl, const char* encoding,
+ const char** ignorelist, const char** skiplist, bool dynamic_tbl)
+{
+ xg_tag_tbl = tbl;
+ dynamic_tag_tbl = dynamic_tbl;
+
+ xml_common_init(fname, encoding, ignorelist, skiplist);
+}
+
+void
+xml_init(const QString& fname, xg_tag_mapping* tbl, const char* encoding,
+ const char** ignorelist, const char** skiplist)
+{
+ xg_tag_tbl = new QList;
+ dynamic_tag_tbl = true;
+ for (xg_tag_mapping* tm = tbl; tm->tag_cb != nullptr; ++tm) {
+ auto* cb = new XgFunctionPtrCallback(tm->tag_cb);
+ xg_tag_tbl->append({cb, tm->cb_type, tm->tag_name});
+ }
+
+ xml_common_init(fname, encoding, ignorelist, skiplist);
+}
+
void
xml_deinit()
{
+ if (dynamic_tag_tbl) {
+ for (const auto& tm : qAsConst(*xg_tag_tbl)) {
+ delete tm.tag_cb;
+ }
+ delete xg_tag_tbl;
+ }
+ xg_tag_tbl = nullptr;
+
reader_data.clear();
rd_fname.clear();
- xg_tag_tbl = nullptr;
+
xg_encoding = nullptr;
codec = utf8_codec;
+
delete xg_shortcut_taglist;
xg_shortcut_taglist = nullptr;
}
@@ -129,7 +165,7 @@ xml_shortcut(const QStringRef& name)
static void
xml_run_parser(QXmlStreamReader& reader)
{
- xg_callback* cb;
+ XgCallbackBase* cb;
QString current_tag;
while (!reader.atEnd()) {
@@ -163,7 +199,7 @@ xml_run_parser(QXmlStreamReader& reader)
cb = xml_tbl_lookup(current_tag, cb_start);
if (cb) {
const QXmlStreamAttributes attrs = reader.attributes();
- cb(nullptr, &attrs);
+ (*cb)(nullptr, &attrs);
}
cb = xml_tbl_lookup(current_tag, cb_cdata);
@@ -173,7 +209,7 @@ xml_run_parser(QXmlStreamReader& reader)
// thus we will not process the EndElement case as we will issue a readNext first.
// does a caller ever expect to be able to use both a cb_cdata and a
// cb_end callback?
- cb(c, nullptr);
+ (*cb)(c, nullptr);
current_tag.chop(reader.qualifiedName().length() + 1);
}
break;
@@ -185,7 +221,7 @@ xml_run_parser(QXmlStreamReader& reader)
cb = xml_tbl_lookup(current_tag, cb_end);
if (cb) {
- cb(reader.name().toString(), nullptr);
+ (*cb)(reader.name().toString(), nullptr);
}
current_tag.chop(reader.qualifiedName().length() + 1);
break;
diff --git a/xmlgeneric.h b/xmlgeneric.h
index c1e97fc82..c4e14dfe2 100644
--- a/xmlgeneric.h
+++ b/xmlgeneric.h
@@ -31,23 +31,126 @@
// be convenient to overload some day.
using xg_string = const QString&;
-
enum xg_cb_type {
cb_start = 1,
cb_cdata,
cb_end,
};
-using xg_callback = void (xg_string, const QXmlStreamAttributes*);
+class XgCallbackBase
+{
+public:
+ XgCallbackBase() = default;
+ virtual ~XgCallbackBase() = default;
+ XgCallbackBase(const XgCallbackBase&) = delete;
+ XgCallbackBase& operator=(const XgCallbackBase&) = delete;
+ XgCallbackBase(XgCallbackBase&&) = delete;
+ XgCallbackBase& operator=(XgCallbackBase&&) = delete;
+
+ virtual void operator()(xg_string string, const QXmlStreamAttributes* attrs) const = 0;
+};
+
+template
+class XgFunctor : public XgCallbackBase
+{
+public:
+ using XgCb = void (XgFormat::*)(xg_string, const QXmlStreamAttributes*);
+ XgFunctor(XgFormat* obj, XgCb cb) : that_(obj), cb_(cb) {}
+ void operator()(xg_string string, const QXmlStreamAttributes* attrs) const override
+ {
+ (that_->*cb_)(string, attrs);
+ }
+private:
+ XgFormat* that_;
+ XgCb cb_;
+};
+
+class XgFunctionPtrCallback : public XgCallbackBase
+{
+public:
+ using XgCb = void (xg_string, const QXmlStreamAttributes*);
+ explicit XgFunctionPtrCallback(XgCb cb) : cb_(cb) {}
+ void operator()(xg_string string, const QXmlStreamAttributes* attrs) const override
+ {
+ (*cb_)(string, attrs);
+ }
+
+private:
+ XgCb* cb_;
+};
+
+// xml processing uses a QList.
+// You may generated this yourself. See method 1 below.
+// Or it may be generated for you using one of the subsequent
+// methods.
+struct xg_tag_map_entry {
+ XgCallbackBase* tag_cb;
+ xg_cb_type cb_type;
+ const char* tag_name;
+};
+
+// Table generation from an array containing function pointers.
+// The above table can be generated by xml_init. See method 2 below.
+// This is how things done historically before the Format class was
+// introduced.
+using xg_callback = void (xg_string, const QXmlStreamAttributes*);
struct xg_tag_mapping {
xg_callback* tag_cb;
xg_cb_type cb_type;
const char* tag_name;
};
-extern const char* xhtml_entities;
+// Table generation from a list containing member function pointers.
+// The above table can be generated by xml_init. See method 3 below.
+template
+struct xg_functor_map_entry {
+ using XgCb = void (MyFormat::*)(xg_string, const QXmlStreamAttributes*);
+ XgCb tag_cb;
+ xg_cb_type cb_type;
+ const char* tag_name;
+};
+
+template
+QList* build_xg_tag_map(MyFormat* instance, const QList& map)
+{
+ auto* tag_tbl = new QList;
+ for (const auto& entry : qAsConst(map)) {
+ auto* tag_cb = new XgFunctor(instance, entry.tag_cb);
+ tag_tbl->append({tag_cb, entry.cb_type, entry.tag_name});
+ }
+ return tag_tbl;
+}
+/*
+ * There are multiple ways to initialize with xml_init.
+ *
+ * 1. Build your own QList, and pass it.
+ * You own the table, you must do any required clean up.
+ * Your callbacks may be a mix of function pointers wrapped in XgFunctors
+ * and non-static member functions wrapped in XgFunctionPtrCallbacks.
+ * and XgFunctionPtrCallback(for static member functions or global functions) entries.
+ * xml_init(fname, tbl, encoding, ignorelist, skiplist, false);
+ * You must set the dynamic_tbl parameter to false so xml_deninit doesn't
+ * attempt to free the table resources when xml_deinit is called.
+ *
+ * 2. Have xml_init build and own a table of XgFunctionPtrCallback entries
+ * from an array of function pointers, i.e. a xg_tag_mapping array.
+ * This only works when all callbacks are function pointers.
+ * xml_init(fname, tbl, encoding, ignorelist, skiplist);
+ * Generated table entries will automatically be freed.
+ *
+ * 3. Have xml_init build and own a table of XgFunctor entries from a list
+ * of non-static member functions, i.e. a QList.
+ * This only works when all callbacks are non-static member functions.
+ * xml_init(fname, build_xg_tag_map(instance, map), encoding, ignorelist, skiplist, true);
+ * You must set the dynamic_tbl parameter to true to free the generated table
+ * resources when xml_deinit is called.
+ *
+ */
+void xml_init(const QString& fname, QList* tbl, const char* encoding,
+ const char** ignorelist = nullptr,
+ const char** skiplist = nullptr, bool dynamic_tbl = false);
void xml_init(const QString& fname, xg_tag_mapping* tbl,const char* encoding,
const char** ignorelist = nullptr,
const char** skiplist = nullptr);
diff --git a/yahoo.cc b/yahoo.cc
index 72d312621..21c520056 100644
--- a/yahoo.cc
+++ b/yahoo.cc
@@ -20,36 +20,19 @@
*/
+#include // for QXmlStreamAttributes
+
#include "defs.h"
#include "yahoo.h"
-#include "xmlgeneric.h"
-#include
+#include "xmlgeneric.h" // for xg_string, build_xg_tag_map, xml_deinit, xml_init, xml_read
#define MYNAME "yahoo"
-// static xg_callback wpt_s, wpt_lat, wpt_lon, wpt_e;
-// static xg_callback wpt_addr /*, wpt_city, wpt_state, wpt_zip, wpt_country*/;
-#if 0
-static xg_tag_mapping gl_map[] = {
- { wpt_s, cb_start, "/ResultSet/Result" },
- { wpt_lat, cb_cdata, "/ResultSet/Result/Latitude" },
- { wpt_lon, cb_cdata, "/ResultSet/Result/Longitude" },
- { wpt_addr, cb_cdata, "/ResultSet/Result/Address" },
- { wpt_addr, cb_cdata, "/ResultSet/Result/City" },
- { wpt_addr, cb_cdata, "/ResultSet/Result/State" },
- { wpt_addr, cb_cdata, "/ResultSet/Result/Zip" },
- { wpt_addr, cb_cdata, "/ResultSet/Result/Country" },
- { wpt_e, cb_end, "/ResultSet/Result" },
- { nullptr, (xg_cb_type)0, nullptr}
-};
-#endif
-
void
YahooFormat::rd_init(const QString& fname)
{
-abort();
-// xml_init(fname, gl_map, nullptr);
+ xml_init(fname, build_xg_tag_map(this, gl_map), nullptr, nullptr, nullptr, true);
}
void
@@ -97,20 +80,3 @@ YahooFormat::wpt_addr(xg_string args, const QXmlStreamAttributes*)
}
wpt_tmp->notes += args;
}
-#if 0
-ff_vecs_t yahoo_vecs = {
- ff_type_file,
- { ff_cap_read, ff_cap_none, ff_cap_none },
- yahoo_rd_init,
- nullptr,
- yahoo_rd_deinit,
- nullptr,
- yahoo_read,
- nullptr,
- nullptr,
- &yahoo_args,
- CET_CHARSET_ASCII, 0 /* CET-REVIEW */
- , NULL_POS_OPS,
- nullptr
-};
-#endif
diff --git a/yahoo.h b/yahoo.h
index 1dc7fead0..16d846afb 100644
--- a/yahoo.h
+++ b/yahoo.h
@@ -21,13 +21,14 @@
#ifndef YAHOO_H_INCLUDED_
#define YAHOO_H_INCLUDED_
+#include // for QList
#include // for QString
-#include // for QStringList
-#include
+#include // for QVector
+#include // for QXmlStreamAttributes
#include "defs.h"
#include "format.h" // for Format
-#include "xmlgeneric.h" // for Format
+#include "xmlgeneric.h" // for xg_tag_map_entry, cb_cdata, XgFunctor, cb_end, cb_start
class YahooFormat : public Format
{
@@ -42,7 +43,8 @@ public:
return ff_type_file;
}
- QVector get_cap() const override {
+ QVector get_cap() const override
+ {
return {
ff_cap_read, // waypoints
ff_cap_none, // tracks
@@ -50,11 +52,13 @@ public:
};
}
- QString get_encode() const override {
+ QString get_encode() const override
+ {
return CET_CHARSET_ASCII;
}
- int get_fixed_encode() const override {
+ int get_fixed_encode() const override
+ {
return 0;
}
@@ -63,24 +67,34 @@ public:
void rd_deinit() override;
private:
- Waypoint* wpt_tmp;
- char* as;
-
- QVector yahoo_args = {
- {
- "addrsep", &as,
- "String to separate concatenated address fields (default=\", \")",
- ", ", ARGTYPE_STRING, ARG_NOMINMAX, nullptr
- },
- };
- void wpt_s(const QString &, const QXmlStreamAttributes *);
-
- void wpt_e(const QString &, const QXmlStreamAttributes *);
- void wpt_lat(const QString &, const QXmlStreamAttributes *);
- void wpt_lon(const QString &, const QXmlStreamAttributes *);
- void wpt_addr(const QString &, const QXmlStreamAttributes *);
-// xg_tag_mapping gl_map[];
-
+ Waypoint* wpt_tmp;
+ char* as;
+
+ QVector yahoo_args = {
+ {
+ "addrsep", &as,
+ "String to separate concatenated address fields (default=\", \")",
+ ", ", ARGTYPE_STRING, ARG_NOMINMAX, nullptr
+ },
+ };
+
+ void wpt_s(const QString&, const QXmlStreamAttributes*);
+ void wpt_e(const QString&, const QXmlStreamAttributes*);
+ void wpt_lat(const QString&, const QXmlStreamAttributes*);
+ void wpt_lon(const QString&, const QXmlStreamAttributes*);
+ void wpt_addr(const QString&, const QXmlStreamAttributes*);
+
+ QList> gl_map = {
+ {&YahooFormat::wpt_s, cb_start, "/ResultSet/Result"},
+ {&YahooFormat::wpt_lat, cb_cdata, "/ResultSet/Result/Latitude"},
+ {&YahooFormat::wpt_lon, cb_cdata, "/ResultSet/Result/Longitude"},
+ {&YahooFormat::wpt_addr, cb_cdata, "/ResultSet/Result/Address"},
+ {&YahooFormat::wpt_addr, cb_cdata, "/ResultSet/Result/City"},
+ {&YahooFormat::wpt_addr, cb_cdata, "/ResultSet/Result/State"},
+ {&YahooFormat::wpt_addr, cb_cdata, "/ResultSet/Result/Zip"},
+ {&YahooFormat::wpt_addr, cb_cdata, "/ResultSet/Result/Country"},
+ {&YahooFormat::wpt_e, cb_end, "/ResultSet/Result"}
+ };
};
#endif // YAHOO_H_INCLUDED_
--
2.30.2